using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Pikapika.Models;
using System.Data.Objects.DataClasses;
using System.Data.Objects;
using System.Linq.Expressions;
using System.Web.Routing;

namespace Pikapika.Areas.Admin.Controllers
{
    [Authorize(Roles="Administrators,Editors")]
    public abstract class CrudControllerBase<T> : Controller where T : EntityObject, new()
    {
        private static string _entitySetName;

        private static Lazy<Action<T, int>> _setIdDelegate = new Lazy<Action<T, int>>(() =>
        {
            // compiles to: (model, id) => model.Id = id
            var modelParam = Expression.Parameter(typeof(T), "model");
            var idParam = Expression.Parameter(typeof(int), "id");
            return Expression.Lambda<Action<T, int>>(
                       Expression.Assign(
                           Expression.MakeMemberAccess(modelParam, typeof(T).GetProperty("Id")),
                           idParam
                       ),
                       modelParam, idParam
                   ).Compile();
        });

        private static Lazy<Func<T, int>> _getIdDelegate = new Lazy<Func<T, int>>(() =>
        {
            // compiles to: (model) => model.Id
            var modelParam = Expression.Parameter(typeof(T), "model");
            return Expression.Lambda<Func<T, int>>(
                       Expression.MakeMemberAccess(modelParam, typeof(T).GetProperty("Id")),
                       modelParam
                   ).Compile();
        });

        private static Lazy<Func<int, Expression<Func<T, bool>>>> _idPredicateDelegate = new Lazy<Func<int, Expression<Func<T, bool>>>>(() =>
        {
            // returns expression: model => model.Id == <id>
            var modelParam = Expression.Parameter(typeof(T), "model");
            var member = Expression.MakeMemberAccess(modelParam, typeof(T).GetProperty("Id"));
            return (id) => Expression.Lambda<Func<T, bool>>(
                                Expression.Equal(
                                    member,
                                    Expression.Constant(id)
                                ),
                                modelParam
                        );
        });

        protected abstract ObjectSet<T> GetObjectSet(PikapikaDBEntities ctx);

        protected virtual IQueryable<T> GetObjectQuery(PikapikaDBEntities ctx)
        {
            return GetObjectSet(ctx);
        }

        private Expression<Func<T, bool>> GetIdPredicate(int id){
            return _idPredicateDelegate.Value.Invoke(id);
        }

        private void SetId(T model, int id)
        {
            _setIdDelegate.Value.Invoke(model, id);
        }
        private int GetId(T model)
        {
            return _getIdDelegate.Value.Invoke(model);
        }




        public virtual ActionResult Index()
        {
            using (var ctx = new PikapikaDBEntities())
            {
                return View(GetObjectQuery(ctx).ToList());
            }
        }

        protected virtual ActionResult SingleObjectView(int id)
        {
            using (var ctx = new PikapikaDBEntities())
            {
                var pub = GetObjectQuery(ctx).Where(GetIdPredicate(id)).FirstOrDefault();
                if (pub == null)
                {
                    return HttpNotFound();
                }

                return View(pub);
            }
        }

        private void EnsureEntitySetName(PikapikaDBEntities ctx)
        {
            if (_entitySetName == null)
            {
                _entitySetName = GetObjectSet(ctx).EntitySet.Name;
            }
        }

        //
        // GET: /Publisher/Details/5

        public virtual ActionResult Details(int id)
        {
            return SingleObjectView(id);
        }

        //
        // GET: /Publisher/Create

        public virtual ActionResult Create()
        {
            return View();
        }

        //
        // POST: /Publisher/Create

        [HttpPost]
        public virtual ActionResult Create(T model)
        {
            if (!ModelState.IsValid)
            {
                return View(model);
            }
            try
            {
                using (var ctx = new PikapikaDBEntities())
                {
                    EnsureEntitySetName(ctx);
                    ctx.AddObject(_entitySetName, model);
                    ctx.SaveChanges();
                }

                return RedirectToAction("Details", new { id = GetId(model) });
            }
            catch (Exception ex)
            {
                this.ModelState.AddModelError(string.Empty, ex.ToString());
                return View(model);
            }
        }

        //
        // GET: /Publisher/Edit/5

        public virtual ActionResult Edit(int id)
        {
            return SingleObjectView(id);
        }

        protected ActionResult EditImpl(int id, T model, bool forceReloadFields = false)
        {
            SetId(model, id);
            if (!ModelState.IsValid)
            {
                return View(model);
            }
            try
            {
                using (var ctx = new PikapikaDBEntities())
                {
                    EnsureEntitySetName(ctx);
                    ctx.AttachTo(_entitySetName, model);
                    ctx.ObjectStateManager.ChangeObjectState(model, System.Data.EntityState.Modified);
                    ctx.SaveChanges();
                    if (forceReloadFields)
                    {
                        ctx.Refresh(RefreshMode.StoreWins, model);
                    }
                }
                return RedirectToAction("Details", new { id = id });
            }
            catch (Exception ex)
            {
                this.ModelState.AddModelError(string.Empty, ex.ToString());
                return View(model);
            }
        }
        //
        // POST: /Publisher/Edit/5

        [HttpPost]
        public virtual ActionResult Edit(int id, T model)
        {
            return EditImpl(id, model);
        }

        //
        // GET: /Publisher/Delete/5

        public virtual ActionResult Delete(int id)
        {
            return SingleObjectView(id);
        }

        //
        // POST: /Publisher/Delete/5

        [HttpPost]
        public virtual ActionResult Delete(int id, string listPageKeyName, int? listPageKeyValue)
        {
            try
            {
                using (var ctx = new PikapikaDBEntities())
                {
                    EnsureEntitySetName(ctx);
                    var model = new T();
                    SetId(model, id);
                    ctx.AttachTo(_entitySetName, model);
                    DeleteObject(ctx, model);
                    ctx.SaveChanges();
                }

                RouteValueDictionary values = null;
                if (listPageKeyName != null)
                {
                    values = new RouteValueDictionary();
                    values[listPageKeyName] = listPageKeyValue.GetValueOrDefault();
                }
                return RedirectToAction("Index", values);
            }
            catch (Exception ex)
            {
                this.ModelState.AddModelError(string.Empty, ex.ToString());
                return SingleObjectView(id);
            }
        }

        protected virtual void DeleteObject(PikapikaDBEntities ctx, T toBeDeleted) 
        {
            ctx.DeleteObject(toBeDeleted);
        }
    }
}
